#!/usr/bin/python
# -*- coding: utf-8 -*-

import UserList
import hungarian
import numpy
import copy
import tool_maths
import math

class DistMatrix(UserList.UserList):
    def __init__(self, width, height):
        self._width = width
        self._height = height
        self.data = [[1.0 for _ in xrange(self._width)]
                          for _ in xrange(self._height)]

    def set(self, x, y, value):
        assert( 0 <= x < self._width)
        assert( 0 <= y < self._height)
        self.data[y][x] = value

    def get(self, x, y):
        assert( 0 <= x < self._width)
        assert( 0 <= y < self._height)
        return self.data[y][x]

    def get_min_max(self) :
      mini = min(self._width,self._height)
      maxi = max(self._width,self._height)
      return (mini,maxi)

    def convert4numpy(self,a) :
      for i,line in enumerate(a) :
        for j,item in enumerate(line) :
          self.data[i][j] = item

    def convert4numpy2(self,a) :
      for i,line in enumerate(a) :
        for j,item in enumerate(line) :
          self.data[j][i] = item

    def convert2numpy(self) :
      matrix2 = []
      for line in self.data :
        matrix2.append(list(line))
      a = numpy.array(matrix2,"float32")
      return a

    def convert2numpy2(self) :
      a = self.convert2numpy()
      a = zip(*a)
      return a

    def convert2numpy_list(self) :
      matrix2 = []
      for line in self.data :
        for val in line :
          matrix2.append(val)
      a = numpy.array(matrix2,"float32")
      return a


class DistDictionnaire(DistMatrix):
    def __init__(self, width, height):
        DistMatrix.__init__(self,width,height)
        self.l1 = ["" for _ in xrange(width)]
        self.l2 = ["" for _ in xrange(height)]
        self.info1 = [[] for _ in xrange(width)]
        self.info2 = [[] for _ in xrange(height)]
        self.class1 = []
        self.class2 = []

    def set_l1(self, x, val):
        self.l1[x] = val
    def set_l2(self,y, val):
        self.l2[y] = val

    def set_info1(self, x, info) :
      self.info1[x] = info
    def set_info2(self, y, info) :
      self.info2[y] = info

    def set_all(self, x, val_x, y, val_y, dist) :
      self.l1[x] = val_x
      self.l2[y] = val_y
      self.data[x][y] = dist

#Ajout de données dans les informations recueillies
# - info1, info2 : associe à chaque element de l1 et l2 des informations
#   usuellement : info1 = (offset_start,offset_end)
# - class1, class2 : classes contenant les rstr qui sont localement inclues
#   les unes dans les autres
# - new_matrix_dist : matrices de distances entre classes d'équivalences
    def built_class1(self):
      for i,elt in enumerate(self.info1) :
        self.add2class1(i)

    def add2class1(self,i) :
      info_i = self.info1[i]
      for id_class,c in enumerate(self.class1) :
        cpt = 0
        for e in c :
          info_elt = self.info1[e]
          if(tool_maths.one_is_in(info_i[0],info_i[1],info_elt[0],info_elt[1])) :
             cpt += 1
        if(cpt > 0) :
          self.class1[id_class].append(i)
          return id_class
      self.class1.append([i])

    def built_class2(self):
      for i,elt in enumerate(self.info2) :
        self.add2class2(i)

    def add2class2(self,i) :
      info_i = self.info2[i]
      for id_class,c in enumerate(self.class2) :
        cpt = 0
        for e in c :
          info_elt = self.info2[e]
          if(tool_maths.one_is_in(info_i[0],info_i[1],info_elt[0],info_elt[1])) :
             cpt += 1
        if(cpt == len(c)) :
          self.class2[id_class].append(i)
          return id_class
      self.class2.append([i])

    def built_matrix_class(self) :
      self.matrix_class = [[1.0 for _ in xrange(len(self.class1))]
                                for _ in xrange(len(self.class2))]
      for i,c1 in enumerate(self.class1) :
        for j,c2 in enumerate(self.class2):
#          self.matrix_class[j][i] = self.get_distance_class_max(c1,c2)
#          self.matrix_class[j][i] = self.get_distance_class_min(c1,c2)
          self.matrix_class[j][i] = self.get_distance_class_avg(c1,c2)
        
    def get_distance_class_min(self, class1, class2) :
      res = []
      for i in class1 :
        for j in class2 :
          dist = self.get(i,j)
          res.append(dist)
      res.sort()
      return res[0]

    def get_distance_class_max(self,class1,class2) :
      res = []
      for i in class1 :
        for j in class2 :
          dist = self.get(i,j)
          res.append(dist)
      res.sort()
      res.reverse()
      return res[0]

    def get_distance_class_avg(self,class1,class2) :
      res = []
      for i in class1 :
        for j in class2 :
          dist = self.get(i,j)
          res.append(dist)
      avg = float(sum(res)) / float(len(res))
      return avg

    def matrix_class2numpy(self) :
      matrix2 = []
      for line in self.matrix_class :
        matrix2.append(list(line))
      a = numpy.array(matrix2,"float32")
      return a

    def matrix_class2numpy_list(self) :
      matrix2 = []
      for line in self.matrix_class :
        for val in line :
          matrix2.append(val)
      a = numpy.array(matrix2,"float32")
      return a

    def get_min_max_class_matrix(self) :
      minDim = min(len(self.matrix_class),len(self.matrix_class[0]))
      maxDim = max(len(self.matrix_class),len(self.matrix_class[0]))
      return (minDim,maxDim)

# Prints divers et variés

    def convert2tabular_tex(self) :
      res = "\\begin{table}[!ht]\n";
      res += "  \\begin{center} \n"
      res += "    \\begin{tabular}"

      def_tabular = ""
      new_l2 = []
      for j,val2 in enumerate(self.l2) :
        new_val = val2.replace(" ","\_")
        new_l2.append("\\rotatebox{310}{\\textsf{%s}}"%(new_val))
        def_tabular += "p{0.4cm}"

      res += "{r" + def_tabular + "}\\\\ \n"
      res += "    & " + " & ".join(new_l2) + "\\\\ \n"

      for i,val1 in enumerate(self.l1) :
        new_val = val1.replace(" ","\_")
        line = "  \\textsf{%s} "%(new_val)
        for j,val2 in enumerate(self.l2) : 
          line += "& %6.2f"%(self.get(i,j))
        res += line + "\\\\ \n"

      res += "    \end{tabular} \n"
      res += "  \end{center} \n"
      res += "\end{table} \n"

      return res

    def convert2tabular_tex_mask(self,mask,level) :
      res = "\\begin{table}[!ht]\n";
      res += "  \\begin{center} \n"
      res += "    \\begin{tabular}"

      def_tabular = ""
      new_l2 = []
      for j,val2 in enumerate(self.l2) :
        new_val = val2.replace(" ","\_")
        new_l2.append("\\rotatebox{310}{\\textsf{%s}}"%(new_val))
        def_tabular += "p{0.4cm}"

      res += "{r" + def_tabular + "}\\\\ \n"
      res += "    & " + " & ".join(new_l2) + "\\\\ \n"

      for i,val1 in enumerate(self.l1) :
        new_val = val1.replace(" ","\_")
        line = "  \\textsf{%s} "%(new_val)
        for j,val2 in enumerate(self.l2) : 
          dist = self.get(i,j)
          line += "& \\textbf{%.2f}"%(dist) if (mask[i][j] >= level) else "& {\\tiny%.2f}"%(dist)
        res += line + "\\\\ \n"

      res += "    \end{tabular} \n"
      res += "  \end{center} \n"
      res += "\end{table} \n"

      return res

    def convert2tex(self) :
      res ="""
      \documentclass[a2paper]{article}
      \usepackage[utf8]{inputenc}
      \usepackage[T1]{fontenc}
      \usepackage[francais]{babel}
      \usepackage[counterclockwise]{rotating}
      \usepackage[table]{xcolor}
      \usepackage[landscape]{geometry}
      \usepackage{lscape}
      \\begin{document}
      %s
      \end{document}"""%(self.convert2tabular_tex())
      return res

    def convert2tex_mask(self,mask,level) :
      res ="""
      \documentclass[a2paper]{article}
      \usepackage[utf8]{inputenc}
      \usepackage[T1]{fontenc}
      \usepackage[francais]{babel}
      \usepackage[counterclockwise]{rotating}
      \usepackage[table]{xcolor}
      \usepackage[landscape]{geometry}
      \usepackage{lscape}
      \\begin{document}
      %s
      \end{document}"""%(self.convert2tabular_tex_mask(mask,level))
      return res

def squarify(_matrix, _val_fill):
    matrix2 = zip(*_matrix)
    if len(_matrix) <= len(_matrix[0]):
      matrix2 = zip(*matrix2)
    len_line = len(matrix2[0])
    for _ in xrange(len_line - len(matrix2)):
      matrix2.append([_val_fill for _ in xrange(len_line)])
    return matrix2

def max_ligne(ligne,level) :
  list_couple = []
  for i,val in enumerate(ligne) :
    list_couple.append((val,i))
  list_couple.sort()
  list_res = []
  for i in range(level+1):
    couple = list_couple[i]
    ni = couple[1]
    list_res.append(ni)
  return list_res

def process_mathmax_aux(matrixOrig,level) :
  void = numpy.array(matrixOrig,"float32")
  void.fill(0)
  for i,ligne in enumerate(matrixOrig) :
    l = copy.copy(ligne)
    res = max_ligne(l,level)
    for ni in res :
      void[i][ni] += 1
  return void

def process_mathmax(matrixOrig,level) :
  void1 = process_mathmax_aux(matrixOrig,level)
  nMatrix = zip(*matrixOrig)
  void2 = process_mathmax_aux(nMatrix,level)
  void2 = zip(*void2)
  void3 = []
  for ligne in void2 :
    void3.append(list(ligne))
  void3 = numpy.array(void3,"float32")
  return (void3 + void1)

def process_hungarian(matrixOrig):
  minDim = min(len(matrixOrig), len(matrixOrig[0]))
  maxDim = max(len(matrixOrig), len(matrixOrig[0]))

  matrix = squarify(matrixOrig,1.)
  a = numpy.array(matrix,"float32")
  pairs = hungarian.lap(a)[0]

  a_void = numpy.resize(a, (minDim,maxDim))
  a_void.fill(1.0)

  for i, j in enumerate(pairs):
    if i >= minDim:
        break;
    a_void[i][j] = matrix[i][j]

  return (a_void,pairs)

def get_around(_matrix,i,j,gap,out_val) :
  max_i = len(_matrix)
  max_j = len(_matrix[0])
  matrix = []
  for ni in range(i-gap,i+gap+1) :
    ligne = []
    for nj in range(j-gap,j+gap+1) :
      if(ni >= max_i or ni < 0 or nj >= max_j or nj < 0) :
        val = out_val
      else :
        val = _matrix[ni][nj]
      ligne.append(val)
    matrix.append(ligne)
  return matrix

def get_average_matrix(_matrix) :
  res = 0.0
  cpt = 0.0
  for line in _matrix :
    for item in line :
      if(item != -1) :
        res += item
        cpt += 1.0
  return (res / cpt)

def get_average_diag(_matrix) :
  res = 0.0
  cpt = 0.0
  for i in range(len(_matrix)) :
    item = _matrix[i][i]
    if(item != -1) :
      res += item
      cpt += 1.0
    else :
      res += 1.0
      cpt += 1.0
  return (res / cpt)

def get_average_diag2(_matrix) :
  res = 0.0
  cpt = 0.0
  for i in range(len(_matrix)) :
    item = _matrix[i][i]
    if(item != -1) :
      res += item
      cpt += 1.0
  return (res / cpt)




def get_best_model1(_matrix,val_max) :
  min1 = min((_matrix[0][0],_matrix[0][1],_matrix[1][0]))
  min2 = min((_matrix[2][2],_matrix[1][2],_matrix[2][1]))
  return (min1,min2)

def get_best_model2(_matrix,val_max):
  min1 = min((_matrix[0][1],_matrix[0][2],_matrix[1][2]))
  min2 = min((_matrix[1][0],_matrix[2][0],_matrix[2][1]))
  return (min1,min2)

def calcul_score_model(val, model, val_bannie) :
  score = val
  cpt = 1.0
  for v in model :    
    if(v != val_bannie) :
      score += v
      cpt += 1.0
  return (score / cpt)

def process_neighbor_model(_matrix) :
  void = numpy.array(_matrix,"float32")
  void.fill(0)
  max_i = len(_matrix)
  max_j = len(_matrix[0])
  for i,line in enumerate(_matrix) :
    for j,val in enumerate(line) :
      matrix_neighbor = get_around(_matrix,i,j,1,1000)
      model1 = get_best_model1(matrix_neighbor,1000)
      model2 = get_best_model2(matrix_neighbor,1000)
      score1 = calcul_score_model(val,model1,1000)
      score2 = calcul_score_model(val,model2,1000)
      void[i][j] = min(score1,score2)
  return void

def process_neighbor_model1(_matrix) :
  void = numpy.array(_matrix,"float32")
  void.fill(0)
  max_i = len(_matrix)
  max_j = len(_matrix[0])
  for i,line in enumerate(_matrix) :
    for j,val in enumerate(line) :
      matrix_neighbor = get_around(_matrix,i,j,1,1000)
      model1 = get_best_model1(matrix_neighbor,1000)
      score1 = calcul_score_model(val,model1,1000)
      void[i][j] = score1
  return void


def get_best_x(_matrix,x) :
  l = []
  mid = int(math.floor(len(_matrix)/2))
  val_mid = _matrix[mid][mid]
  for i,line in enumerate(_matrix) :
    for j,item in enumerate(line) :
      if(item != -1 and i != mid and j != mid) :
        l.append(item)
  l.sort()
  best = l[:x]
  best.append(val_mid)
  return sum(best) / len(best)

def process_neighbor_max2(_matrix) :
  void = numpy.array(_matrix,"float32")
  void.fill(0)
  max_i = len(_matrix)
  max_j = len(_matrix[0])
  for i,line in enumerate(_matrix) :
    for j,item in enumerate(line) :
      matrix_neighbor = get_around(_matrix,i,j,1,-1)
      void[i][j] = get_best_x(matrix_neighbor,2)
  return void

def process_neighbor(_matrix) :
  void = numpy.array(_matrix,"float32")
  void.fill(0)
  max_i = len(_matrix)
  max_j = len(_matrix[0])
  for i,line in enumerate(_matrix) :
    for j,item in enumerate(line) :
      matrix_neighbor = get_around(_matrix,i,j,1,-1)
      void[i][j] = get_average_diag2(matrix_neighbor)
      #void[i][j] = get_average_matrix(matrix_neighbor)
  return void

def process_threshold_min_max(_matrix,_min,_max) :
  void = numpy.array(_matrix,"float32")
  void.fill(0)
  max_i = len(_matrix)
  max_j = len(_matrix[0])
  for i,line in enumerate(_matrix) :
    for j,item in enumerate(line) :
      curr = _matrix[i][j]
      if(curr > _max) :
        curr = 1.0
      elif(curr < _min) :
        curr = 0.0

      void[i][j] = curr
  return void

def process_threshold(_matrix,_thresh) :
  void = numpy.array(_matrix,"float32")
  void.fill(0)
  max_i = len(_matrix)
  max_j = len(_matrix[0])
  for i,line in enumerate(_matrix) :
    for j,item in enumerate(line) :
      curr = _matrix[i][j]
      void[i][j] = curr if (curr < _thresh) else 1.0
  return void
